package com.runyu.cordova.plugin.barcode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.honeywell.aidc.AidcManager;
import com.honeywell.aidc.BarcodeFailureEvent;
import com.honeywell.aidc.BarcodeReadEvent;
import com.honeywell.aidc.BarcodeReader;
import com.honeywell.aidc.BarcodeReader.BarcodeListener;
import com.honeywell.aidc.ScannerUnavailableException;
import com.runyu.cordova.plugin.JsActionSupport;

import android.graphics.Point;

public class BarcodeEventDispatcher implements BarcodeListener, AidcManagerCreatedListener, JsActionSupport {
	private AidcManager manager;
	private BarcodeReader reader;
	private List<CallbackContext> callbackContexts = new ArrayList<>();
	private List<CallbackContext> readyCallbackContexts = new ArrayList<>();

	private static final String EVENT_TYPE_REGISTER = "register";
	private static final String EVENT_TYPE_READ = "barcode";
	private static final String ACTION_ADD_BARCODE_EVENT_LISTENER = "addBarcodeEventListener";
	private static final String ACTION_REMOVE_BARCODE_EVENT_LISTENER = "removeBarcodeEventListener";
	private static final String ACTION_BARCODE_DEVICE_READY = "barcodeDeviceReady";

	public void addBacordeCallbackContext(CallbackContext callbackContext) {
		JSONObject jsonObject = new JSONObject();
		if (this.manager == null || this.reader == null) {
			try {
				jsonObject.put("type", EVENT_TYPE_REGISTER);
				jsonObject.put("message", "BarcodeReader is not initialized");
			} catch (JSONException e) {
				e.printStackTrace();
			}
			callbackContext.error(jsonObject);
		} else {
			callbackContexts.add(callbackContext);
			try {
				jsonObject.put("type", EVENT_TYPE_REGISTER);
				jsonObject.put("callbackId", callbackContext.getCallbackId());
			} catch (JSONException e) {
				e.printStackTrace();
			}
			PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, jsonObject);
			pluginResult.setKeepCallback(true);
			callbackContext.sendPluginResult(pluginResult);
		}
	}

	public void destroy() {
		if (reader != null) {
			reader.release();
		}
		if (manager != null) {
			manager.close();
		}
		if (callbackContexts != null) {
			callbackContexts.clear();
		}
	}

	private AreaPoint cvtPoint(Point point) {
		AreaPoint areaPoint = new AreaPoint();
		areaPoint.setX(point.x);
		areaPoint.setY(point.y);
		return areaPoint;
	}

	private BarcodeEventInfo cvtBarcodeReadEvent(BarcodeReadEvent event) {
		BarcodeEventInfo barcodeEventInfo = new BarcodeEventInfo();
		String aimId = event.getAimId();
		String barcodeData = event.getBarcodeData();
		String charset = event.getCharset().toString();
		String codeId = event.getCodeId();
		String timestamp = event.getTimestamp();
		barcodeEventInfo.setAimId(aimId);
		barcodeEventInfo.setCharset(charset);
		barcodeEventInfo.setCodeId(codeId);
		barcodeEventInfo.setTimestamp(timestamp);
		barcodeEventInfo.setBarcodeData(barcodeData);
		List<Point> barcodeBounds = event.getBarcodeBounds();
		List<AreaPoint> aps = new ArrayList<>();
		for (Point p : barcodeBounds) {
			aps.add(this.cvtPoint(p));
		}
		barcodeEventInfo.setBarcodeBounds(aps);
		return barcodeEventInfo;
	}

	@Override
	public void onBarcodeEvent(BarcodeReadEvent event) {
		BarcodeEventInfo barcodeEventInfo = this.cvtBarcodeReadEvent(event);
		for (CallbackContext callbackContext : this.callbackContexts) {
			JSONObject jsonObject = barcodeEventInfo.toJson();
			try {
				jsonObject.put("type", EVENT_TYPE_READ);
			} catch (JSONException e) {
				e.printStackTrace();
			}
			PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, barcodeEventInfo.toJson());
			pluginResult.setKeepCallback(true);
			callbackContext.sendPluginResult(pluginResult);
		}
	}

	@Override
	public void onFailureEvent(BarcodeFailureEvent event) {
		for (CallbackContext callbackContext : this.callbackContexts) {
			PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, "BarcodeFailureEvent");
			pluginResult.setKeepCallback(true);
			callbackContext.sendPluginResult(pluginResult);
		}
	}

	public void onDestroy() {
		this.destroy();
	}

	public boolean removeBarcodeCallbackContext(String callbackId) {
		CallbackContext needRemoveCallbackContext = null;
		for (CallbackContext callbackContext : this.callbackContexts) {
			if (callbackContext.getCallbackId().equals(callbackId)) {
				needRemoveCallbackContext = callbackContext;
				break;
			}
		}
		if (needRemoveCallbackContext != null) {
			this.callbackContexts.remove(needRemoveCallbackContext);
		}
		return true;
	}

	public void onResume() {
		if (reader != null) {
			try {
				reader.claim();
			} catch (ScannerUnavailableException e) {
				e.printStackTrace();
			}
		}
	}

	public void addReadyCallbackContext(CallbackContext callbackContext) {
		if (this.manager != null) {
			callbackContext.success();
			return;
		}
		synchronized (this.readyCallbackContexts) {
			if (this.manager != null) {
				callbackContext.success();
				return;
			}
			this.readyCallbackContexts.add(callbackContext);
		}
	}

	@Override
	public void onCreated(AidcManager aidcManager) {
		this.manager = aidcManager;
		this.createBarcodeReader();
		synchronized (this.readyCallbackContexts) {
			for (CallbackContext readyCallbackContext : this.readyCallbackContexts) {
				readyCallbackContext.success();
			}
			this.readyCallbackContexts.clear();
		}
	}

	private void createBarcodeReader() {
		try {
			reader = manager.createBarcodeReader();
			// apply settings
			Map<String, Object> properties = new HashMap<String, Object>();
			// Set Symbologies On/Off
			properties.put(BarcodeReader.PROPERTY_CODE_128_ENABLED, true);
			properties.put(BarcodeReader.PROPERTY_GS1_128_ENABLED, true);
			properties.put(BarcodeReader.PROPERTY_QR_CODE_ENABLED, true);
			properties.put(BarcodeReader.PROPERTY_CODE_39_ENABLED, true);
			properties.put(BarcodeReader.PROPERTY_DATAMATRIX_ENABLED, true);
			properties.put(BarcodeReader.PROPERTY_UPC_A_ENABLE, true);
			properties.put(BarcodeReader.PROPERTY_EAN_13_ENABLED, false);
			properties.put(BarcodeReader.PROPERTY_AZTEC_ENABLED, false);
			properties.put(BarcodeReader.PROPERTY_CODABAR_ENABLED, false);
			properties.put(BarcodeReader.PROPERTY_INTERLEAVED_25_ENABLED, false);
			properties.put(BarcodeReader.PROPERTY_PDF_417_ENABLED, false);
			properties.put(BarcodeReader.PROPERTY_CODE_93_ENABLED, true);
			// Set Max Code 39 barcode length
			properties.put(BarcodeReader.PROPERTY_CODE_39_MAXIMUM_LENGTH, 10);
			// Turn on center decoding
			properties.put(BarcodeReader.PROPERTY_CENTER_DECODE, true);
			// Enable bad read response
			properties.put(BarcodeReader.PROPERTY_NOTIFICATION_BAD_READ_ENABLED, true);
			// Sets time period for decoder timeout in any mode
			properties.put(BarcodeReader.PROPERTY_DECODER_TIMEOUT, 400);
			// Apply the settings
			reader.setProperties(properties);
			reader.addBarcodeListener(BarcodeEventDispatcher.this);
			reader.claim();
		} catch (Exception e) {
			e.printStackTrace();

		}
	}

	@Override
	public List<String> getSupportAction() {
		List<String> actions = new ArrayList<>();
		actions.add(ACTION_ADD_BARCODE_EVENT_LISTENER);
		actions.add(ACTION_REMOVE_BARCODE_EVENT_LISTENER);
		actions.add(ACTION_BARCODE_DEVICE_READY);
		return actions;
	}

	@Override
	public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
		if (ACTION_ADD_BARCODE_EVENT_LISTENER.equals(action)) {
			this.addBacordeCallbackContext(callbackContext);
			return true;
		}
		if (ACTION_REMOVE_BARCODE_EVENT_LISTENER.equals(action)) {
			if (args.length() > 0) {
				Object object = args.get(0);
				String callbackId = object.toString();
				boolean success = this.removeBarcodeCallbackContext(callbackId);
				if (!success) {
					callbackContext.error("Remove barcode event listener failed");
					return false;
				}
			}
			callbackContext.success();
			return true;
		}
		if (ACTION_BARCODE_DEVICE_READY.equals(action)) {
			this.addReadyCallbackContext(callbackContext);
			return true;
		}
		return false;
	}
}
